/*
 * $Id: iptc.c 143 2011-10-28 14:02:47Z hyunghwan.chung $
 *
   Copyright 2005-2009 Chung, Hyung-Hwan.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an  BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

#include "iptc.h"
#include <xp/bas/memory.h>
#include <xp/bas/string.h>
#include <xp/bas/mstring.h>
#include <xp/bas/locale.h>
#include <xp/bas/endian.h>
#include <xp/bas/search.h>
#include <xp/bas/assert.h>
#include <xp/bas/stdlib.h>

static struct ipt_entry* __make_entry (
	xp_iptc_t*              iptc,
	const xp_char_t*        tgtnam, /* target name */
	const xp_iptc_matip_t*  matip,  /* match IP */
	const xp_iptc_matpro_t* matpro, /* match protocol data */
	const xp_iptc_matxtn_t* matxtn, /* match extensions */
	const xp_iptc_tgtopt_t* tgtopt  /* target options */
);

#define LOAD_IPTC_IF_NECESSARY(iptc) \
	do { if (!iptc->loaded && __load_iptc(iptc) == -1) return -1; } while(0)

static int __load_iptc (xp_iptc_t* iptc)
{
	iptc->handle = iptc_init (iptc->table);	
	if (iptc->handle == XP_NULL) return -1;
	iptc->loaded = xp_true;
	return 0;
}

xp_iptc_t* xp_iptc_open (xp_iptc_t* iptc, const xp_char_t* table_name)
{
	xp_size_t n;

	if (iptc == XP_NULL) {
		iptc = (xp_iptc_t*)xp_malloc (XP_SIZEOF(xp_iptc_t));
		if (iptc == XP_NULL) return XP_NULL;
		xp_memset (iptc, 0, sizeof(*iptc));
		iptc->__dynamic = xp_true;
	}
	else 
	{
		xp_memset (iptc, 0, sizeof(*iptc));
		iptc->__dynamic = xp_false;
	}

#ifdef XP_CHAR_IS_MCHAR
	n = xp_strxcpy (iptc->table, xp_countof(iptc->table), table_name);
	if (n < xp_strlen(table_name)) return XP_NULL;
#else	
	n = xp_wcstomcs (table_name, iptc->table, xp_countof(iptc->table));
	if (n == (xp_size_t)-1 || n < xp_strlen(table_name)) return XP_NULL;
#endif

	iptc->loaded = xp_false;
	if (__load_iptc(iptc) == -1) {
		if (iptc->__dynamic) xp_free (iptc);
		return XP_NULL;
	}

	iptc->entry = XP_NULL;
	iptc->entry_capacity = 0;

	return iptc;
}

void xp_iptc_close (xp_iptc_t* iptc)
{
	if (iptc->loaded) {
		iptc_free (&iptc->handle);
		iptc->loaded = xp_false;
	}
	if (iptc->entry != XP_NULL) {
		xp_free (iptc->entry);
		iptc->entry = XP_NULL;
	}

	if (iptc->__dynamic) xp_free (iptc);
}

int xp_iptc_commit (xp_iptc_t* iptc)
{
	if (iptc->loaded &&
	    iptc_commit(&iptc->handle) == 0) return -1;
	iptc->loaded = xp_false;
	return 0;
}

void xp_iptc_abort (xp_iptc_t* iptc)
{
	if (iptc->loaded) {
		iptc_free (&iptc->handle);
		iptc->loaded = xp_false;
	}
}

int xp_iptc_reset (xp_iptc_t* iptc)
{
	if (iptc->loaded) {
		iptc_free (&iptc->handle);
		iptc->loaded = xp_false;
	}

	return __load_iptc(iptc);
}

void xp_iptc_set_changed (xp_iptc_t* iptc)
{
	iptc_set_changed (iptc->handle);
}

int xp_iptc_create_chain (xp_iptc_t* iptc, const xp_char_t* name)
{
	ipt_chainlabel chain;

#ifdef XP_CHAR_IS_MCHAR
	xp_strxcpy (chain, xp_countof(chain), name);
#else
	xp_size_t n;
	n = xp_wcstomcs (name, chain, xp_countof(chain));
	if (n == (xp_size_t)-1 || n < xp_strlen(name)) return -1;
#endif

	LOAD_IPTC_IF_NECESSARY (iptc);

	/* check if the chain exists */
	if (iptc_is_chain (chain, iptc->handle)) return -1;

	/* otherwise create the chain */
	if (iptc_create_chain (chain, &iptc->handle) == 0) return -1;
	return 0;
}

int xp_iptc_delete_chain (xp_iptc_t* iptc, const xp_char_t* name)
{
	ipt_chainlabel chain;

#ifdef XP_CHAR_IS_MCHAR
	xp_strxcpy (chain, xp_countof(chain), name);
#else
	xp_size_t n;
	n = xp_wcstomcs (name, chain, xp_countof(chain));
	if (n == (xp_size_t)-1 || n < xp_strlen(name)) return -1;
#endif

	LOAD_IPTC_IF_NECESSARY (iptc);

	if (!iptc_is_chain (chain, iptc->handle)) return -1;
	if (iptc_delete_chain (chain, &iptc->handle) == 0) return -1;
	return 0;
}

int xp_iptc_zero_chain (xp_iptc_t* iptc, const xp_char_t* name)
{
	ipt_chainlabel chain;

#ifdef XP_CHAR_IS_MCHAR
	xp_strxcpy (chain, xp_countof(chain), name);
#else
	xp_size_t n;
	n = xp_wcstomcs (name, chain, xp_countof(chain));
	if (n == (xp_size_t)-1 || n < xp_strlen(name)) return -1;
#endif

	LOAD_IPTC_IF_NECESSARY (iptc);

	if (iptc_zero_entries (chain, &iptc->handle) == 0) return -1;
	return 0;
}

int xp_iptc_flush_chain (xp_iptc_t* iptc, const xp_char_t* name)
{
	ipt_chainlabel chain;

#ifdef XP_CHAR_IS_MCHAR
	xp_strxcpy (chain, xp_countof(chain), name);
#else
	xp_size_t n;
	n = xp_wcstomcs (name, chain, xp_countof(chain));
	if (n == (xp_size_t)-1 || n < xp_strlen(name)) return -1;
#endif

	LOAD_IPTC_IF_NECESSARY (iptc);

	if (iptc_flush_entries (chain, &iptc->handle) == 0) return -1;
	return 0;
}

int xp_iptc_walk_chain (xp_iptc_t* iptc, 
	int (*func) (xp_iptc_t*, const xp_char_t*))
{
	int ret = 0;
	const xp_mchar_t *chain;
	xp_mchar_t *chains;

	xp_size_t i, chaincount = 0;

	LOAD_IPTC_IF_NECESSARY (iptc);

	chain = iptc_first_chain(&iptc->handle);
	while (chain != XP_NULL) {
		chaincount++;
		chain = iptc_next_chain(&iptc->handle);
	}

	chains = (xp_mchar_t*)xp_malloc(XP_SIZEOF(ipt_chainlabel) * chaincount);
	if (chains == XP_NULL) return -1;

	i = 0; chain = iptc_first_chain(&iptc->handle);
	while (chain != XP_NULL) {
		xp_mstrcpy(&chains[i*XP_SIZEOF(ipt_chainlabel)], chain);
		i++; chain = iptc_next_chain(&iptc->handle);
	}

	for (i = 0; i < chaincount; i++) {
		xp_mchar_t* c = &chains[i*XP_SIZEOF(ipt_chainlabel)];

		/* not allowed to delete builtin chains */
		if (iptc_builtin (c, iptc->handle) && 
		    func == xp_iptc_delete_chain) continue;

#ifdef XP_CHAR_IS_MCHAR
		if (func (iptc, c) == -1) {
			ret = -1;
			break;
		}
#else
		xp_wchar_t name[XP_SIZEOF(ipt_chainlabel)];
		xp_size_t n;
		n = xp_mcstowcs (c, name, xp_countof(name));
		if (n == (xp_size_t)-1 || n < xp_mstrlen(c)) {
			ret = -1;
			break;
		}

		if (func(iptc, name) == -1) {
			ret = -1;
			break;
		}
#endif
	}

	xp_free (chains);
	return ret;
}

const xp_iptc_entry_t* xp_iptc_first_rule (
	xp_iptc_t* iptc, const xp_char_t* name,
	xp_iptc_entry_t const** rule_end)
{
	ipt_chainlabel chain;
	const xp_iptc_entry_t* tmp;

#ifdef XP_CHAR_IS_MCHAR
	xp_strxcpy (chain, xp_countof(chain), name);
#else
	xp_size_t n;
	n = xp_wcstomcs (name, chain, xp_countof(chain));
	if (n == (xp_size_t)-1 || n < xp_strlen(name)) return XP_NULL;
#endif

	tmp = iptc_first_rule (chain, &iptc->handle);

	/* dirty hack to work around libiptc bugs */
	*rule_end = iptc_get_cache_rule_end (iptc->handle);
	return tmp;
}

const xp_iptc_entry_t* xp_iptc_next_rule (
	xp_iptc_t* iptc, const xp_iptc_entry_t* previous,
	xp_iptc_entry_t const** rule_end)
{
	const xp_iptc_entry_t* x;
	/* dirty hack to work around libiptc bugs */
	iptc_set_cache_rule_end (*rule_end, iptc->handle);

	x = iptc_next_rule (previous, &iptc->handle);
	*rule_end = iptc_get_cache_rule_end (iptc->handle);
	return x;
}


int xp_iptc_insert_entry (
	xp_iptc_t*              iptc,
	const xp_char_t*        chanam, 
	xp_size_t               pos,
	const xp_char_t*        tgtnam,
	const xp_iptc_matip_t*  matip,
	const xp_iptc_matpro_t* matpro,
	const xp_iptc_matxtn_t* matxtn,
	const xp_iptc_tgtopt_t* tgtopt)
{
	xp_size_t n;
	ipt_chainlabel chain;
	struct ipt_entry* entry;

#ifdef XP_CHAR_IS_MCHAR
	n = xp_strxcpy (chain, xp_countof(chain), chanam);
	if (n < xp_strlen(chanam)) return -1;
#else
	n = xp_wcstomcs (chanam, chain, xp_countof(chain));
	if (n == (xp_size_t)-1 || n < xp_strlen(chanam)) return -1;
#endif

	LOAD_IPTC_IF_NECESSARY (iptc);

	entry = __make_entry (iptc, tgtnam,  matip, matpro, matxtn, tgtopt);
	if (entry == XP_NULL) return -1;

	if (iptc_insert_entry (chain, entry, pos, &iptc->handle) == 0) {
		/* xp_free (entry); */
		return -1;
	}

	/* xp_free (entry); */
	return 0;
}


int xp_iptc_append_entry (
	xp_iptc_t*              iptc, 
	const xp_char_t*        chanam, 
	const xp_char_t*        tgtnam,
	const xp_iptc_matip_t*  matip,
	const xp_iptc_matpro_t* matpro,
	const xp_iptc_matxtn_t* matxtn,
	const xp_iptc_tgtopt_t* tgtopt)
{
	xp_size_t n;
	ipt_chainlabel chain;
	struct ipt_entry* entry;

#ifdef XP_CHAR_IS_MCHAR
	n = xp_strxcpy (chain, xp_countof(chain), chanam);
	if (n < xp_strlen(chanam)) return -1;
#else
	n = xp_wcstomcs (chanam, chain, xp_countof(chain));
	if (n == (xp_size_t)-1 || n < xp_strlen(chanam)) return -1;
#endif

	LOAD_IPTC_IF_NECESSARY (iptc);

	entry = __make_entry (iptc, tgtnam,  matip, matpro, matxtn, tgtopt);
	if (entry == XP_NULL) return -1;

	if (iptc_append_entry (chain, entry, &iptc->handle) == 0) {
		/* xp_free (entry); */
		return -1;
	}

	/* xp_free (entry); */
	return 0;
}

int xp_iptc_delete_entry (
	xp_iptc_t*              iptc, 
	const xp_char_t*        chanam, 
	const xp_char_t*        tgtnam,
	const xp_iptc_matip_t*  matip,
	const xp_iptc_matpro_t* matpro,
	const xp_iptc_matxtn_t* matxtn,
	const xp_iptc_tgtopt_t* tgtopt)
{
	xp_size_t n;
	ipt_chainlabel chain;
	struct ipt_entry* entry;
	xp_byte_t* match_mask;

#ifdef XP_CHAR_IS_MCHAR
	n = xp_strxcpy (chain, xp_countof(chain), chanam);
	if (n < xp_strlen(chanam)) return -1;
#else
	n = xp_wcstomcs (chanam, chain, xp_countof(chain));
	if (n == (xp_size_t)-1 || n < xp_strlen(chanam)) return -1;
#endif

	LOAD_IPTC_IF_NECESSARY (iptc);

	entry = __make_entry (iptc, tgtnam,  matip, matpro, matxtn, tgtopt);
	if (entry == XP_NULL) return -1;

	match_mask = (unsigned char *)xp_malloc (entry->next_offset);
	if (match_mask == XP_NULL) {
		xp_free (entry);
		return -1;
	}
	xp_memset (match_mask, 0xFF, entry->next_offset);

	if (iptc_delete_entry (chain, entry, match_mask, &iptc->handle) == 0) {
		xp_free (match_mask);
		/* xp_free (entry); */
		return -1;
	}

	xp_free (match_mask);
	/* xp_free (entry); */
	return 0;
}

int xp_iptc_delete_entry_at (
	xp_iptc_t* iptc, const xp_char_t* chanam, xp_size_t pos)
{
	xp_size_t n;
	ipt_chainlabel chain;

#ifdef XP_CHAR_IS_MCHAR
	n = xp_strxcpy (chain, xp_countof(chain), chanam);
	if (n < xp_strlen(chanam)) return -1;
#else
	n = xp_wcstomcs (chanam, chain, xp_countof(chain));
	if (n == (xp_size_t)-1 || n < xp_strlen(chanam)) return -1;
#endif

	if (iptc_delete_num_entry (chain, pos, &iptc->handle) == 0) {
		return -1;
	}

	return 0;
}

static int compstr (const void* s1, const void* s2, void* ctx)
{
	return xp_strcmp ((const xp_char_t*)s1, *(const xp_char_t**)s2);
}

int xp_parseiptcmatxtnhashlimitavg (
	const xp_char_t* tok, xp_size_t len, xp_uint32_t *val)
{
	/* taken and modified from libipt_hashlimit.c */

	const xp_char_t* delim;
	xp_uint32_t r;
	xp_uint32_t mult = 1;  /* Seconds by default. */

	delim = xp_strxchr(tok, len, XP_T('/'));
	if (delim) 
	{
		const xp_char_t* unit = delim + 1;
		xp_size_t ulen = len - (delim - tok + 1);
		if (ulen <= 0) return -1; /* no unit after slash */

		if (xp_strxcasecmp(unit, ulen, XP_T("sec")) == 0)
			mult = 1;
		else if (xp_strxcasecmp(unit, ulen, XP_T("min")) == 0)
			mult = 60;
		else if (xp_strxcasecmp(unit, ulen, XP_T("hour")) == 0)
			mult = 60*60;
		else if (xp_strxcasecmp(unit, ulen, XP_T("day")) == 0)
			mult = 24*60*60;
		else
			return -1;
	}

	XP_STRXTOI(r,tok,len,XP_NULL,10);
	if (r <= 0) return -1;

	/* This would get mapped to infinite (1/day is minimum they
           can specify, so we're ok at that end). */
	if (r / mult > IPT_HASHLIMIT_SCALE) return -1; /* rate too fast */

	*val = IPT_HASHLIMIT_SCALE * mult / r;
	return 0;
}

#define PORT_IN_FULL_RANGE(p)  ((p)->start == 0x0000 && (p)->end == 0xFFFF)

static void copy_interface (xp_mchar_t* name, xp_mchar_t* mask, xp_size_t size, const xp_mchar_t* src)
{
	xp_size_t n;

	xp_memset (name, 0, size);
	xp_memset (mask, 0, size);

	n = xp_mstrxcpy (name, size, src);

	if (n > 0) 
	{
		/* include the null-terminator in match */
		xp_memset (mask, 0xFF, n + 1);
		if (n >= 2 && name[n-1] == XP_MT('+'))
		{
			mask[n-1] = 0x00; /* kill + */
			mask[n] = 0x00; /* kill null */
		}
	}
}

static struct ipt_entry* __make_entry (
	xp_iptc_t*              iptc,
	const xp_char_t*        tgtnam, /* target name */
	const xp_iptc_matip_t*  matip,  /* match IP */
	const xp_iptc_matpro_t* matpro, /* match protocol data */
	const xp_iptc_matxtn_t* matxtn, /* match extensions */
	const xp_iptc_tgtopt_t* tgtopt  /* target options */ )
{
	struct ipt_entry* entry = XP_NULL;
	struct ipt_entry_match* match = XP_NULL, * match_base = XP_NULL;
	struct ipt_entry_target* target, * target_base = XP_NULL;
	xp_size_t n, size, match_base_size, target_base_size;
	xp_size_t match_offset[8] = { 0 },  match_size[8] = { 0 };
	xp_size_t target_offset[1] = { 0 }, target_size[1] = { 0 }; 
	int target_revision[1] = { 0 };
#ifdef XP_CHAR_IS_MCHAR
	const xp_char_t* dev_in = XP_NULL;
	const xp_char_t* dev_out = XP_NULL;
#else
	xp_mchar_t dev_in[IFNAMSIZ] = "";
	xp_mchar_t dev_out[IFNAMSIZ] = "";
#endif
	enum 
	{
		/* keep the follow enum values in the same 
		 * alphabetical order as the tgtnames array */
		TARGET_CLASSIFY,
		TARGET_DNAT,
		TARGET_LVS,
		TARGET_MARK,
		TARGET_MASQUERADE,
		TARGET_NOTRACK,
		TARGET_REDIRECT,
		TARGET_REJECT,
		TARGET_SNAT,

		/* keep this as the last item */
		TARGET_NORMAL
	} target_type;

#ifdef XP_CHAR_IS_MCHAR
	if (matip->inifce)
	{
		dev_in = matip->inifce;
		if (xp_strlen(dev_in) >= IFNAMSIZ) return XP_NULL;
	}
	if (matip->outifce)
	{
		dev_out = matip->outifce;
		if (xp_strlen(dev_out) >= IFNAMSIZ) return XP_NULL;
	}
#else
	if (matip->inifce)
	{
		n = xp_wcstomcs (matip->inifce, dev_in, xp_countof(dev_in));
		if (n == (xp_size_t)-1 || n < xp_strlen(matip->inifce)) return XP_NULL;
	}
	if (matip->outifce)
	{
		n = xp_wcstomcs (matip->outifce, dev_out, xp_countof(dev_out));
		if (n == (xp_size_t)-1 || n < xp_strlen(matip->outifce)) return XP_NULL;
	}
#endif

	{
		static const xp_char_t* tgtnames[] =
		{
			/* NOTE: keep these in the alphabetical order */
			XP_T("CLASSIFY"),
			XP_T("DNAT"),
			XP_T("LVS"),
			XP_T("MARK"),
			XP_T("MASQUERADE"),
			XP_T("NOTRACK"),
			XP_T("REDIRECT"),
			XP_T("REJECT"),
			XP_T("SNAT")
		};

		const xp_char_t** ptr;
		ptr = (const xp_char_t**) xp_bsearch (
			tgtnam, tgtnames, XP_COUNTOF(tgtnames), 
			XP_SIZEOF(tgtnames[0]), XP_NULL, compstr
		);
		target_type = ptr? (ptr - tgtnames): TARGET_NORMAL;
	}
		
	/* decide the match size */
	match_base_size = 0;
	if (matpro)
	{
		switch (matip->proto)
		{
			case IPPROTO_TCP:
			{
		    		if (matpro->invflags & XP_IPTC_MATPRO_SRCPORT ||
				    matpro->invflags & XP_IPTC_MATPRO_DSTPORT ||
				    !PORT_IN_FULL_RANGE(&matpro->port.src) ||
				    !PORT_IN_FULL_RANGE(&matpro->port.dst))
				{
					match_offset[0] = match_base_size;
					match_size[0] = 
						XP_SIZEOF(struct ipt_entry_match) + 
						IPT_ALIGN(XP_SIZEOF(struct ipt_tcp));
					match_base_size += match_size[0];
				}
				break;
			}
	
			case IPPROTO_UDP:
			{
		    		if (matpro->invflags & XP_IPTC_MATPRO_SRCPORT ||
				    matpro->invflags & XP_IPTC_MATPRO_DSTPORT ||
				    !PORT_IN_FULL_RANGE(&matpro->port.src) ||
				    !PORT_IN_FULL_RANGE(&matpro->port.dst))
				{
					match_offset[0] = match_base_size;
					match_size[0] = 
						XP_SIZEOF(struct ipt_entry_match) + 
						IPT_ALIGN(XP_SIZEOF(struct ipt_udp));
					match_base_size += match_size[0];
				}
				break;
			}
	
			case IPPROTO_ICMP:
			{
				match_offset[0] = match_base_size;
				match_size[0] = 
					XP_SIZEOF(struct ipt_entry_match) +
					IPT_ALIGN(XP_SIZEOF(struct ipt_icmp));
				match_base_size += match_size[0];
				break;
			}
		}
	}

	if (matxtn)
	{
		if (matxtn->flags & XP_IPTC_MATXTN_MAC)
		{
			match_offset[1] = match_base_size;
			match_size[1] = 
				XP_SIZEOF(struct ipt_entry_match) +
				IPT_ALIGN(XP_SIZEOF(struct ipt_mac_info));
			match_base_size += match_size[1];
		}
		if (matxtn->flags & XP_IPTC_MATXTN_RATE)
		{
			match_offset[2] = match_base_size;
			match_size[2] = 
				XP_SIZEOF(struct ipt_entry_match) +
				IPT_ALIGN(XP_SIZEOF(struct ipt_rateinfo));
			match_base_size += match_size[2];
		}
		if (matxtn->flags & XP_IPTC_MATXTN_MARK)
		{
			match_offset[3] = match_base_size;
			match_size[3] = 
				XP_SIZEOF(struct ipt_entry_match) +
				IPT_ALIGN(XP_SIZEOF(struct ipt_mark_info));
			match_base_size += match_size[3];
		}
		if (matxtn->flags & XP_IPTC_MATXTN_HASHLIMIT)
		{
			match_offset[4] = match_base_size;
			match_size[4] = 
				XP_SIZEOF(struct ipt_entry_match) +
				IPT_ALIGN(XP_SIZEOF(struct ipt_hashlimit_info));
			match_base_size += match_size[4];
		}
		if (matxtn->flags & XP_IPTC_MATXTN_CONNLIMIT)
		{
			match_offset[5] = match_base_size;
			match_size[5] = 
				XP_SIZEOF(struct ipt_entry_match) +
				IPT_ALIGN(XP_SIZEOF(struct ipt_connlimit_info));
			match_base_size += match_size[5];
		}
		if (matxtn->flags & XP_IPTC_MATXTN_PHYSDEV)
		{
			match_offset[6] = match_base_size;
			match_size[6] = 
				XP_SIZEOF(struct ipt_entry_match) +
				IPT_ALIGN(XP_SIZEOF(struct ipt_physdev_info));
			match_base_size += match_size[6];
		}
		if (matxtn->flags & XP_IPTC_MATXTN_STATE)
		{
			match_offset[7] = match_base_size;
			match_size[7] = 
				XP_SIZEOF(struct ipt_entry_match) +
				IPT_ALIGN(XP_SIZEOF(struct ipt_state_info));
			match_base_size += match_size[7];
		}
	
	}

	/* decide the target size */
	target_base_size = 0;
	switch (target_type)
	{
		case TARGET_REDIRECT:
		case TARGET_DNAT:
		case TARGET_SNAT:
		case TARGET_MASQUERADE:
		{
			target_offset[0] = target_base_size;
			target_size[0] = 
				XP_SIZEOF(struct ipt_entry_target) +
				IPT_ALIGN(XP_SIZEOF(struct ip_nat_multi_range));
			target_base_size += target_size[0];
			break;
		}

		case TARGET_MARK:
		{
			target_offset[0] = target_base_size;
		#if defined(HAVE_STRUCT_XT_MARK_TGINFO2)
			target_size[0] = 
				XP_SIZEOF(struct ipt_entry_target) +
				IPT_ALIGN(XP_SIZEOF(struct ipt_mark_target_info));
		#else
			if (tgtopt->mark.op != XP_IPTC_TGTOPT_MARK_SET)
			{
				target_size[0] = 
					XP_SIZEOF(struct ipt_entry_target) +
					IPT_ALIGN(XP_SIZEOF(struct ipt_mark_target_info_v1));
				target_revision[0] = 1;
			}
			else
			{
				target_size[0] = 
					XP_SIZEOF(struct ipt_entry_target) +
					IPT_ALIGN(XP_SIZEOF(struct ipt_mark_target_info));
			}
		#endif
			target_base_size += target_size[0];
			break;
		}

		case TARGET_REJECT:
		{
			target_offset[0] = target_base_size;
			target_size[0] = 
				XP_SIZEOF(struct ipt_entry_target) +
				IPT_ALIGN(XP_SIZEOF(struct ipt_reject_info));
			target_base_size += target_size[0];
			break;
		}

		case TARGET_NOTRACK:
		case TARGET_LVS:
		{
			target_offset[0] = target_base_size;
			target_size[0] = XP_SIZEOF(struct ipt_entry_target);
			target_base_size += target_size[0];
			break;
		}

		case TARGET_CLASSIFY:
		{
			target_offset[0] = target_base_size;
			target_size[0] = 
				XP_SIZEOF(struct ipt_entry_target) +
				IPT_ALIGN(XP_SIZEOF(struct ipt_classify_target_info));
			target_base_size += target_size[0];
			break;
		}

		default:
		{
			target_offset[0] = target_base_size;
			target_size[0] = 
				XP_SIZEOF(struct ipt_entry_target) +
				IPT_ALIGN(XP_SIZEOF(int));
			target_base_size += target_size[0];
			break;
		}
	}

	size = XP_SIZEOF(struct ipt_entry) + match_base_size + target_base_size;

	#define XXXX 10240
	{
		xp_size_t cap = (size / XXXX + 1) * XXXX;
		if (cap > iptc->entry_capacity) 
		{
			if (iptc->entry != XP_NULL) xp_free (iptc->entry);
			iptc->entry = (struct ipt_entry*)xp_malloc (cap);
			if (iptc->entry == XP_NULL) iptc->entry_capacity = 0;
			else iptc->entry_capacity = cap;
		}
		entry = iptc->entry;
	}
	#undef XXXX
	if (entry == XP_NULL) return XP_NULL;

	/* build main entry */
	xp_memset (entry, 0, size);
	match_base = (struct ipt_entry_match*)
		((xp_byte_t*)entry + XP_SIZEOF(struct ipt_entry));
	target_base = (struct ipt_entry_target*)
		((xp_byte_t*)entry + XP_SIZEOF(struct ipt_entry) + match_base_size);

	entry->target_offset = XP_SIZEOF(struct ipt_entry) + match_base_size;
	entry->next_offset = size;

	entry->ip.src.s_addr = matip->src.ip;
	entry->ip.dst.s_addr = matip->dst.ip;
	entry->ip.smsk.s_addr = matip->src.mask;
	entry->ip.dmsk.s_addr = matip->dst.mask; 
	if (matip->inifce)
	{
		copy_interface (entry->ip.iniface, entry->ip.iniface_mask, xp_countof(entry->ip.iniface), dev_in);
	}
	if (matip->outifce)
	{
		copy_interface (entry->ip.outiface, entry->ip.outiface_mask, xp_countof(entry->ip.outiface), dev_out);
	}
	entry->ip.proto = matip->proto;

	if (matip->invflags & XP_IPTC_MATIP_SRCIP) 
		entry->ip.invflags |= IPT_INV_SRCIP;
	if (matip->invflags & XP_IPTC_MATIP_DSTIP) 
		entry->ip.invflags |= IPT_INV_DSTIP;
	if (matip->invflags & XP_IPTC_MATIP_INIFCE) 
		entry->ip.invflags |= IPT_INV_VIA_IN;
	if (matip->invflags & XP_IPTC_MATIP_OUTIFCE) 
		entry->ip.invflags |= IPT_INV_VIA_OUT;
	if (matip->invflags & XP_IPTC_MATIP_PROTO) 
		entry->ip.invflags |= IPT_INV_PROTO;

	/* build protocol part */
	if (matpro && match_size[0] > 0)
	{
		switch (matip->proto)
		{
			case IPPROTO_TCP:
			{
				struct ipt_tcp* data;
	
				match = (struct ipt_entry_match*)
					((xp_byte_t*)match_base + match_offset[0]);
				data = (struct ipt_tcp*)match->data;
			
				match->u.match_size = match_size[0];
				xp_mstrxcpy (match->u.user.name, 
						xp_countof(match->u.user.name), "tcp");
	
				data->spts[0] = matpro->port.src.start;
				data->spts[1] = matpro->port.src.end;
				data->dpts[0] = matpro->port.dst.start;
				data->dpts[1] = matpro->port.dst.end;
	
				if (matpro->invflags & XP_IPTC_MATPRO_SRCPORT) 
					data->invflags |= IPT_TCP_INV_SRCPT;
				if (matpro->invflags & XP_IPTC_MATPRO_DSTPORT)
					data->invflags |= IPT_TCP_INV_DSTPT;

				break;
			}
	
			case IPPROTO_UDP:
			{
				struct ipt_udp* data;
	
				match = (struct ipt_entry_match*)
					((xp_byte_t*)match_base + match_offset[0]);
				data = (struct ipt_udp*)match->data;
			
				match->u.match_size = match_size[0];
				xp_mstrxcpy (match->u.user.name, 
					xp_countof(match->u.user.name), "udp");
		
				data->spts[0] = matpro->port.src.start;
				data->spts[1] = matpro->port.src.end;
				data->dpts[0] = matpro->port.dst.start;
				data->dpts[1] = matpro->port.dst.end;
		
				if (matpro->invflags & XP_IPTC_MATPRO_SRCPORT) 
					data->invflags |= IPT_TCP_INV_SRCPT;
				if (matpro->invflags & XP_IPTC_MATPRO_DSTPORT)
					data->invflags |= IPT_TCP_INV_DSTPT;

				break;
			}
	
			case IPPROTO_ICMP:
			{
				struct ipt_icmp* data;
				match = (struct ipt_entry_match*)
					((xp_byte_t*)match_base + match_offset[0]);
				data = (struct ipt_icmp*)match->data;
		
				match->u.match_size = match_size[0];
				xp_mstrxcpy (match->u.user.name, 
					xp_countof(match->u.user.name), "icmp");
		
				data->type = matpro->icmp.type;
				data->code[0] = matpro->icmp.code[0];
				data->code[1] = matpro->icmp.code[1];
	
				if (matpro->invflags & XP_IPTC_MATPRO_ICMP) 
					data->invflags = 1;

				break;
			}
		}
	}

	/* build match extensions */
	if (matxtn)
	{
		if (matxtn->flags & XP_IPTC_MATXTN_MAC)
		{
			struct ipt_mac_info* data;
			match = (struct ipt_entry_match*)
				((xp_byte_t*)match_base + match_offset[1]);
			data = (struct ipt_mac_info*)match->data;

			match->u.match_size = match_size[1];
			xp_mstrxcpy (match->u.user.name, 
				xp_countof(match->u.user.name), "mac");
			xp_memcpy (data->srcaddr, matxtn->mac.src, XP_SIZEOF(data->srcaddr));
			if (matxtn->invflags & XP_IPTC_MATXTN_MAC) data->invert = 1;
		}

		if (matxtn->flags & XP_IPTC_MATXTN_RATE)
		{
			struct ipt_rateinfo* data;
			match = (struct ipt_entry_match*)
				((xp_byte_t*)match_base + match_offset[2]);
			data = (struct ipt_rateinfo*)match->data;

			/* 
			 * 1/10,000 sec period => max of 10,000/sec.  
			 * Min rate is then 429490 seconds, or one every 59 hours. 
			 */

			match->u.match_size = match_size[2];
			xp_mstrxcpy (match->u.user.name, 
				xp_countof(match->u.user.name), "limit");

			/* Average secs between packets * scale */
			data->avg = matxtn->rate.avg;
			/* Period multiplier for upper limit. */
			data->burst = matxtn->rate.burst;
		}

		if (matxtn->flags & XP_IPTC_MATXTN_MARK)
		{
			struct ipt_mark_info* data;
			match = (struct ipt_entry_match*)
				((xp_byte_t*)match_base + match_offset[3]);
			data = (struct ipt_mark_info*)match->data;

			match->u.match_size = match_size[3];
			xp_mstrxcpy (match->u.user.name, 
				xp_countof(match->u.user.name), "mark");

			data->mark = matxtn->mark.value;
			data->mask = matxtn->mark.mask;

			if (matxtn->invflags & XP_IPTC_MATXTN_MARK) data->invert = 1;
		}

		if (matxtn->flags & XP_IPTC_MATXTN_HASHLIMIT)
		{
			/* no inversion(invflags) for hashlimit */
			struct ipt_hashlimit_info* data;

			match = (struct ipt_entry_match*)
				((xp_byte_t*)match_base + match_offset[4]);
			data = (struct ipt_hashlimit_info*)match->data;

			match->u.match_size = match_size[4];
			xp_mstrxcpy (match->u.user.name, 
				xp_countof(match->u.user.name), "hashlimit");

			/* --hashlimit */
			data->cfg.avg = matxtn->hashlimit.avg;

			/* --hashlimit-burst */
			data->cfg.burst = matxtn->hashlimit.burst;

			/* --hashlimit-htable-size */
			data->cfg.size = matxtn->hashlimit.size;

			/* --hashlimit-htable-max */
			data->cfg.max = matxtn->hashlimit.max;

			/* --hashlimit-htable-gcinterval */
			data->cfg.gc_interval = matxtn->hashlimit.gcinterval;

			/* --hashlimit-htable-expire */
			data->cfg.expire = matxtn->hashlimit.expire;

			/* --hashlimit-mode */
			data->cfg.mode = matxtn->hashlimit.mode;
		
			/* --hashlimit-name */
#ifdef XP_CHAR_IS_MCHAR
			xp_strxcpy (
				data->name, xp_countof(data->name), 
				matxtn->hashlimit.name);
#else
			xp_wcstomcs (matxtn->hashlimit.name, 
				data->name, xp_countof(data->name));
#endif
		}

		if (matxtn->flags & XP_IPTC_MATXTN_CONNLIMIT)
		{
			struct ipt_connlimit_info* data;

			match = (struct ipt_entry_match*)
				((xp_byte_t*)match_base + match_offset[5]);
			data = (struct ipt_connlimit_info*)match->data;

			match->u.match_size = match_size[5];
			xp_mstrxcpy (match->u.user.name, 
				xp_countof(match->u.user.name), "connlimit");

			data->inverse = matxtn->connlimit.invert;
			data->limit = matxtn->connlimit.limit;

			/* use memset as data->mask may not be a scalar type
			 * if xp_connlimit_info suports IPv6 */
			if (matxtn->connlimit.mask_pfxlen == 0)
			{
				xp_memset (&data->mask, 0, sizeof(data->mask));
			}
			else
			{
				xp_uint32_t mask;
				mask = xp_hton32 ((~(xp_uint32_t)0) << (32 - matxtn->connlimit.mask_pfxlen));
				xp_memcpy (&data->mask, &mask, XP_SIZEOF(mask));
			}
		}

		if (matxtn->flags & XP_IPTC_MATXTN_PHYSDEV)
		{
			struct ipt_physdev_info* data;

			match = (struct ipt_entry_match*)
				((xp_byte_t*)match_base + match_offset[6]);
			data = (struct ipt_physdev_info*)match->data;

			match->u.match_size = match_size[6];
			xp_mstrxcpy (match->u.user.name, 
				xp_countof(match->u.user.name), "physdev");

			if ((matxtn->physdev.flags & XP_IPTC_MATXTN_PHYSDEV_IN) && matxtn->physdev.indev)
			{
			#if defined(XP_CHAR_IS_MCHAR)
				const xp_char_t* iface = matxtn->physdev.indev;
			#else
				xp_mchar_t iface[IFNAMSIZ] = "";
				xp_wcstomcs (matxtn->physdev.indev, iface, xp_countof(iface));
			#endif

				data->bitmask |= IPT_PHYSDEV_OP_IN;

				if (!(matxtn->physdev.invflags & XP_IPTC_MATXTN_PHYSDEV_IN) !=
				    !(matxtn->invflags & XP_IPTC_MATXTN_PHYSDEV))
				{
					/* the inversion flag is set on either one of the fields */
					data->invert |= IPT_PHYSDEV_OP_IN;
				}

				copy_interface (data->physindev, data->in_mask, XP_COUNTOF(data->physindev), iface);
			}
			if ((matxtn->physdev.flags & XP_IPTC_MATXTN_PHYSDEV_OUT) && matxtn->physdev.outdev)
			{
			#if defined(XP_CHAR_IS_MCHAR)
				const xp_char_t* iface = matxtn->physdev.outdev;
			#else
				xp_mchar_t iface[IFNAMSIZ] = "";
				xp_wcstomcs (matxtn->physdev.outdev, iface, xp_countof(iface));
			#endif

				data->bitmask |= IPT_PHYSDEV_OP_OUT;

				if (!(matxtn->physdev.invflags & XP_IPTC_MATXTN_PHYSDEV_OUT) !=
				    !(matxtn->invflags & XP_IPTC_MATXTN_PHYSDEV))
				{
					/* the inversion flag is set on either one of the fields */
					data->invert |= IPT_PHYSDEV_OP_OUT;
				}

				copy_interface (data->physoutdev, data->out_mask, XP_COUNTOF(data->physoutdev), iface);
			}

			if (matxtn->physdev.flags & XP_IPTC_MATXTN_PHYSDEV_IS_IN)
			{
				data->bitmask |= IPT_PHYSDEV_OP_ISIN;

				if (!(matxtn->physdev.invflags & XP_IPTC_MATXTN_PHYSDEV_IS_IN) !=
				    !(matxtn->invflags & XP_IPTC_MATXTN_PHYSDEV))
				{
					/* the inversion flag is set on either one of the fields */
					data->invert |= IPT_PHYSDEV_OP_ISIN;
				}
			}

			if (matxtn->physdev.flags & XP_IPTC_MATXTN_PHYSDEV_IS_OUT)
			{
				data->bitmask |= IPT_PHYSDEV_OP_ISOUT;

				if (!(matxtn->physdev.invflags & XP_IPTC_MATXTN_PHYSDEV_IS_OUT) !=
				    !(matxtn->invflags & XP_IPTC_MATXTN_PHYSDEV))
				{
					/* the inversion flag is set on either one of the fields */
					data->invert |= IPT_PHYSDEV_OP_ISOUT;
				}
			}

			if (matxtn->physdev.flags & XP_IPTC_MATXTN_PHYSDEV_IS_BRIDGED)
			{
				data->bitmask |= IPT_PHYSDEV_OP_BRIDGED;

				if (!(matxtn->physdev.invflags & XP_IPTC_MATXTN_PHYSDEV_IS_BRIDGED) !=
				    !(matxtn->invflags & XP_IPTC_MATXTN_PHYSDEV))
				{
					/* the inversion flag is set on either one of the fields */
					data->invert |= IPT_PHYSDEV_OP_BRIDGED;
				}
			}
		}

		if (matxtn->flags & XP_IPTC_MATXTN_STATE)
		{
			struct ipt_state_info* data;

			match = (struct ipt_entry_match*)
				((xp_byte_t*)match_base + match_offset[7]);
			data = (struct ipt_state_info*)match->data;

			match->u.match_size = match_size[7];
			xp_mstrxcpy (match->u.user.name, 
				xp_countof(match->u.user.name), "state");

			data->statemask = 0;
			if (matxtn->state.masks & XP_IPTC_MATXTN_STATE_INVALID)
				data->statemask |= IPT_STATE_INVALID;
			if (matxtn->state.masks & XP_IPTC_MATXTN_STATE_NEW)
				data->statemask |= IPT_STATE_BIT(IP_CT_NEW);
			if (matxtn->state.masks & XP_IPTC_MATXTN_STATE_ESTABLISHED)
				data->statemask |= IPT_STATE_BIT(IP_CT_ESTABLISHED);
			if (matxtn->state.masks & XP_IPTC_MATXTN_STATE_RELATED)
				data->statemask |= IPT_STATE_BIT(IP_CT_RELATED);
			if (matxtn->state.masks & XP_IPTC_MATXTN_STATE_UNTRACKED)
				data->statemask |= IPT_STATE_UNTRACKED;

			if (matxtn->invflags & XP_IPTC_MATXTN_STATE) 
				data->statemask = ~data->statemask;
		}
	}

	/* build target */
	target = (struct ipt_entry_target*)
		((xp_byte_t*)target_base + target_offset[0]);
	target->u.user.target_size = target_size[0];
#ifdef XP_CHAR_IS_MCHAR
	xp_strxcpy (target->u.user.name, 
		xp_countof(target->u.user.name), tgtnam);
#else
	xp_wcstomcs (tgtnam, 
		target->u.user.name, xp_countof(target->u.user.name));
#endif
	target->u.user.revision = target_revision[0];

	switch (target_type)
	{
		case TARGET_REDIRECT:
		{
			struct ip_nat_multi_range* data;
			data = (struct ip_nat_multi_range*)target->data;
	
			data->rangesize = 1;
			data->range[0].flags = 0;
	
			// TODO: more protocol check...
			if (matip->proto == IPPROTO_TCP)
			{
				data->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
				data->range[0].min.tcp.port = xp_hton16(tgtopt->redirect.port.start);
				data->range[0].max.tcp.port = xp_hton16(tgtopt->redirect.port.end);
			}
			else if (matip->proto == IPPROTO_UDP)
			{
				data->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
				data->range[0].min.udp.port = xp_hton16(tgtopt->redirect.port.start);
				data->range[0].max.udp.port = xp_hton16(tgtopt->redirect.port.end);
			}
			break;
		}
	
		case TARGET_SNAT:
		{
			struct ip_nat_multi_range* data;
			data = (struct ip_nat_multi_range*)target->data;
	
			data->rangesize = 1;
			data->range[0].flags = IP_NAT_RANGE_MAP_IPS;
			data->range[0].min_ip = tgtopt->snat.ipaddr.start;
			data->range[0].max_ip = tgtopt->snat.ipaddr.end;
	
			if (tgtopt->snat.port.start > 0 && tgtopt->snat.port.end > 0) 
			{
				if (matip->proto == IPPROTO_TCP) 
				{
					data->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
					data->range[0].min.tcp.port = xp_hton16(tgtopt->snat.port.start);
					data->range[0].max.tcp.port = xp_hton16(tgtopt->snat.port.end);
				}
				else if (matip->proto == IPPROTO_UDP) 
				{
					data->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
					data->range[0].min.udp.port = xp_hton16(tgtopt->snat.port.start);
					data->range[0].max.udp.port = xp_hton16(tgtopt->snat.port.end);
				}
			}
			break;
		}

		case TARGET_DNAT:
		{
			struct ip_nat_multi_range* data;
			data = (struct ip_nat_multi_range*)target->data;
	
			data->rangesize = 1;
			data->range[0].flags = IP_NAT_RANGE_MAP_IPS;
			data->range[0].min_ip = tgtopt->dnat.ipaddr.start;
			data->range[0].max_ip = tgtopt->dnat.ipaddr.end;
	
			if (tgtopt->dnat.port.start > 0 && tgtopt->dnat.port.end > 0) 
			{
				if (matip->proto == IPPROTO_TCP) 
				{
					data->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
					data->range[0].min.tcp.port = xp_hton16(tgtopt->dnat.port.start);
					data->range[0].max.tcp.port = xp_hton16(tgtopt->dnat.port.end);
				}
				else if (matip->proto == IPPROTO_UDP) 
				{
					data->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
					data->range[0].min.udp.port = xp_hton16(tgtopt->dnat.port.start);
					data->range[0].max.udp.port = xp_hton16(tgtopt->dnat.port.end);
				}
			}
			break;
		}

		case TARGET_MASQUERADE:
		{
			struct ip_nat_multi_range* data;
			data = (struct ip_nat_multi_range*)target->data;
	
			data->rangesize = 1;
			data->range[0].flags = 0;
	
			if (tgtopt->masquerade.port.start > 0 && tgtopt->masquerade.port.end > 0) 
			{
				if (matip->proto == IPPROTO_TCP) 
				{
					data->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
					data->range[0].min.tcp.port = xp_hton16(tgtopt->masquerade.port.start);
					data->range[0].max.tcp.port = xp_hton16(tgtopt->masquerade.port.end);
				}
				else if (matip->proto == IPPROTO_UDP) 
				{
					data->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
					data->range[0].min.udp.port = xp_hton16(tgtopt->masquerade.port.start);
					data->range[0].max.udp.port = xp_hton16(tgtopt->masquerade.port.end);
				}
			}
			break;
		}

		case TARGET_MARK:
		{
			struct ipt_mark_target_info* data;
			data = (struct ipt_mark_target_info*)target->data;
			
		#if defined(HAVE_STRUCT_XT_MARK_TGINFO2)
			/* kernel sets the mark this way
			 *  skb->mark = (skb->mark & ~data->mask) ^ data->mark; */

			switch (tgtopt->mark.op)
			{
				case XP_IPTC_TGTOPT_MARK_SET:
					data->mark = tgtopt->mark.value;

					/* turn on all mask bits */
					data->mask = 0;
					data->mask = ~data->mask;
					break;

				case XP_IPTC_TGTOPT_MARK_AND:
					data->mark = 0;
					data->mask = ~tgtopt->mark.value;
					break;

				case XP_IPTC_TGTOPT_MARK_OR:
					data->mark = tgtopt->mark.value;
					data->mask = tgtopt->mark.value;
					break;

				// TODO: support XOR as tginfo2 can support it
			}
		#else
			data->mark = tgtopt->mark.value;

			if (tgtopt->mark.op != XP_IPTC_TGTOPT_MARK_SET)
			{
				struct ipt_mark_target_info_v1* v1;
				static int modes[] = { IPT_MARK_SET, IPT_MARK_AND, IPT_MARK_OR };

				v1 = (struct ipt_mark_target_info_v1*)target->data;

				v1->mode = (tgtopt->mark.op >= 0 && tgtopt->mark.op < xp_countof(modes))?
					modes[tgtopt->mark.op]: IPT_MARK_SET;
			}
		#endif
			break;
		}

		case TARGET_REJECT:
		{
			static enum ipt_reject_with types[] = 
			{
				IPT_ICMP_NET_UNREACHABLE,
				IPT_ICMP_HOST_UNREACHABLE,
				IPT_ICMP_PROT_UNREACHABLE,
				IPT_ICMP_PORT_UNREACHABLE,
				IPT_ICMP_NET_PROHIBITED,
				IPT_ICMP_HOST_PROHIBITED,
				IPT_ICMP_ADMIN_PROHIBITED
			};
			struct ipt_reject_info* data;
			data = (struct ipt_reject_info*)target->data;

			data->with = 
				(tgtopt->reject.type >= 0 && 
				 tgtopt->reject.type < xp_countof(types))?
					types[tgtopt->reject.type]: IPT_ICMP_ADMIN_PROHIBITED;

			break;
		}

		case TARGET_CLASSIFY:
		{
			struct ipt_classify_target_info* data;
			data = (struct ipt_classify_target_info*)target->data;
			data->priority = tgtopt->classify.priority;
			break;
		}
	}
 
	return entry;
}

